/*-------------------------------------------------------------------------
 *
 * indexing.c
 *	  此文件包含支持在系统目录上定义索引的常规例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/catalog/indexing.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "executor/executor.h"
#include "utils/rel.h"


/*
 * CatalogOpenIndexes - 打开系统目录上的索引.
 *
 * 在系统目录中插入或更新元组时，调用此函数
 * 为目录更新索引做准备.
 *
 * 在当前实现中，我们与 execUtils.c 共享打开/关闭索引的代码。
 * 但我们不使用 ExecInsertIndexTuples，因为我们
 * 不想创建一个 EState。这意味着我们
 * 不支持系统目录上的部分或表达式索引，
 * 也不能支持广义排除约束。
 * 如果我们愿意承担构建 EState 的额外开销，这可以通过局部更改来解决.
 */
CatalogIndexState
CatalogOpenIndexes(Relation fc_heapRel)
{
	ResultRelInfo *fc_resultRelInfo;

	fc_resultRelInfo = makeNode(ResultRelInfo);
	fc_resultRelInfo->ri_RangeTableIndex = 0;	/* 虚拟占位符 */
	fc_resultRelInfo->ri_RelationDesc = fc_heapRel;
	fc_resultRelInfo->ri_TrigDesc = NULL;	/* 我们不触发触发器 */

	ExecOpenIndices(fc_resultRelInfo, false);

	return fc_resultRelInfo;
}

/*
 * CatalogCloseIndexes - 清理 CatalogOpenIndexes 所分配的资源
 */
void CatalogCloseIndexes(CatalogIndexState fc_indstate)
{
	ExecCloseIndices(fc_indstate);
	pfree(fc_indstate);
}

/*
 * CatalogIndexInsert - 为一个目录元组插入索引条目
 *
 * 这应该针对每个插入或更新的目录元组调用.
 *
 * 这实际上是 ExecInsertIndexTuples 的精简版.
 */
static void fc_CatalogIndexInsert(CatalogIndexState fc_indstate, HeapTuple fc_heapTuple)
{
	int			fc_i;
	int			fc_numIndexes;
	RelationPtr fc_relationDescs;
	Relation	fc_heapRelation;
	TupleTableSlot *fc_slot;
	IndexInfo **fc_indexInfoArray;
	Datum		fc_values[INDEX_MAX_KEYS];
	bool		fc_isnull[INDEX_MAX_KEYS];

	/*
	 * HOT 更新不需要插入索引。但启用断言时我们
	 * 希望检查当前向表/索引插入是否合法.
	 */
#ifndef USE_ASSERT_CHECKING
	if (HeapTupleIsHeapOnly(fc_heapTuple))
		return;
#endif

	/*
	 * 从状态结构中获取信息。如果没有事情要做，就跳出.
	 */
	fc_numIndexes = fc_indstate->ri_NumIndices;
	if (fc_numIndexes == 0)
		return;
	fc_relationDescs = fc_indstate->ri_IndexRelationDescs;
	fc_indexInfoArray = fc_indstate->ri_IndexRelationInfo;
	fc_heapRelation = fc_indstate->ri_RelationDesc;

	/* 需要一个槽来保存正在检查的元组 */
	fc_slot = MakeSingleTupleTableSlot(RelationGetDescr(fc_heapRelation),
									&TTSOpsHeapTuple);
	ExecStoreHeapTuple(fc_heapTuple, fc_slot, false);

	/*
	 * 对于每个索引，形成并插入索引元组
	 */
	for (fc_i = 0; fc_i < fc_numIndexes; fc_i++)
	{
		IndexInfo  *fc_indexInfo;
		Relation	fc_index;

		fc_indexInfo = fc_indexInfoArray[fc_i];
		fc_index = fc_relationDescs[fc_i];

		/* 如果索引标记为只读，则忽略它 */
		if (!fc_indexInfo->ii_ReadyForInserts)
			continue;

		/*
		 * 不支持系统目录上的表达式和部分索引，也不支持排除约束和延迟唯一性.
		 */
		Assert(fc_indexInfo->ii_Expressions == NIL);
		Assert(fc_indexInfo->ii_Predicate == NIL);
		Assert(fc_indexInfo->ii_ExclusionOps == NULL);
		Assert(fc_index->rd_index->indimmediate);
		Assert(fc_indexInfo->ii_NumIndexKeyAttrs != 0);

		/* 请参看上面的检查 */
#ifdef USE_ASSERT_CHECKING
		if (HeapTupleIsHeapOnly(fc_heapTuple))
		{
			Assert(!ReindexIsProcessingIndex(RelationGetRelid(fc_index)));
			continue;
		}
#endif							/* USE_ASSERT_CHECKING */

		/*
		 * FormIndexDatum 用适当的值填充索引列的值和 isnull 参数.
		 */
		FormIndexDatum(fc_indexInfo,
					   fc_slot,
					   NULL,	/* 不需要进行表达式评估 */
					   fc_values,
					   fc_isnull);

		/*
		 * 索引 AM 将完成其余的工作.
		 */
		index_insert(fc_index,		/* 索引关系 */
					 fc_values,	/* 索引 Datums 数组 */
					 fc_isnull,	/* is-null 标志 */
					 &(fc_heapTuple->t_self),	/* 堆元组的 tid */
					 fc_heapRelation,
					 fc_index->rd_index->indisunique ?
					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
					 false,
					 fc_indexInfo);
	}

	ExecDropSingleTupleTableSlot(fc_slot);
}

/*
 * 子程序以验证目录约束是否得到遵守.
 *
 * 通过 CatalogTupleInsert/CatalogTupleUpdate 插入的元组通常是
 * "手工制作的"，因此可能未能满足约束
 * 如果它们由执行器插入，将进行检查。这是
 * 代码错误，因此我们只在启用断言的构建中进行检查.
 */
#ifdef USE_ASSERT_CHECKING

static void fc_CatalogTupleCheckConstraints(Relation fc_heapRel, HeapTuple fc_tup)
{
	/*
	 * 目前，对于系统目录实现的唯一约束是
	 * attnotnull 约束.
	 */
	if (HeapTupleHasNulls(fc_tup))
	{
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_heapRel);
		bits8	   *fc_bp = fc_tup->t_data->t_bits;

		for (int fc_attnum = 0; fc_attnum < fc_tupdesc->natts; fc_attnum++)
		{
			Form_pg_attribute fc_thisatt = TupleDescAttr(fc_tupdesc, fc_attnum);

			Assert(!(fc_thisatt->attnotnull && att_isnull(fc_attnum, fc_bp)));
		}
	}
}

#else							/* !USE_ASSERT_CHECKING */

#define fc_CatalogTupleCheckConstraints(heapRel, tup)  ((void) 0)

#endif							/* USE_ASSERT_CHECKING */

/*
 * CatalogTupleInsert - 为新的目录元组执行堆和索引工作
 *
 * 将“tup”中的元组数据插入到指定的目录关系中。
 *
 * 这是一个方便的例程，用于在系统目录中插入单个元组的常见情况；它插入一个新的堆元组，并保持索引的当前状态。避免在多个元组的情况下使用它，因为打开索引和构建索引信息结构是相对昂贵的。
 * （在这种情况下使用 CatalogTupleInsertWithInfo。）
 */
void CatalogTupleInsert(Relation fc_heapRel, HeapTuple fc_tup)
{
	CatalogIndexState fc_indstate;

	fc_CatalogTupleCheckConstraints(fc_heapRel, fc_tup);

	fc_indstate = CatalogOpenIndexes(fc_heapRel);

	simple_heap_insert(fc_heapRel, fc_tup);

	fc_CatalogIndexInsert(fc_indstate, fc_tup);
	CatalogCloseIndexes(fc_indstate);
}

/*
 * CatalogTupleInsertWithInfo - 与上述相同，但带有调用者提供的索引信息
 *
 * 当跨多个插入时重要的是摊销 CatalogOpenIndexes/CatalogCloseIndexes 的工作时应使用此功能。在某个时刻，我们可能会在某个地方缓存 CatalogIndexState 数据（也许在 relcache 中），以便调用者不必为此烦恼……但我们今天没有这样做。
 */
void CatalogTupleInsertWithInfo(Relation fc_heapRel, HeapTuple fc_tup,
						   CatalogIndexState fc_indstate)
{
	fc_CatalogTupleCheckConstraints(fc_heapRel, fc_tup);

	simple_heap_insert(fc_heapRel, fc_tup);

	fc_CatalogIndexInsert(fc_indstate, fc_tup);
}

/*
 * CatalogTuplesMultiInsertWithInfo - 与上述相同，但适用于多个元组
 *
 * 一次将多个元组插入到给定的目录关系中，摊销 CatalogOpenIndexes 的成本。
 */
void CatalogTuplesMultiInsertWithInfo(Relation fc_heapRel, TupleTableSlot **fc_slot,
								 int fc_ntuples, CatalogIndexState fc_indstate)
{
	/* 无需执行任何操作 */
	if (fc_ntuples <= 0)
		return;

	heap_multi_insert(fc_heapRel, fc_slot, fc_ntuples,
					  GetCurrentCommandId(true), 0, NULL);

	/*
	 * 目录索引没有 heap_multi_insert 的等效项，因此我们必须逐个循环插入。
	 */
	for (int fc_i = 0; fc_i < fc_ntuples; fc_i++)
	{
		bool		fc_should_free;
		HeapTuple	fc_tuple;

		fc_tuple = ExecFetchSlotHeapTuple(fc_slot[fc_i], true, &fc_should_free);
		fc_tuple->t_tableOid = fc_slot[fc_i]->tts_tableOid;
		fc_CatalogIndexInsert(fc_indstate, fc_tuple);

		if (fc_should_free)
			heap_freetuple(fc_tuple);
	}
}

/*
 * CatalogTupleUpdate - 为更新目录元组执行堆和索引工作
 *
 * 用“tup”中的数据替换由“otid”标识的元组。
 *
 * 这是一个方便的例程，用于在系统目录中更新单个元组的常见情况；它更新一个堆元组，并保持索引的当前状态。避免在多个元组的情况下使用它，因为打开索引和构建索引信息结构是相对昂贵的。
 * （在这种情况下使用 CatalogTupleUpdateWithInfo。）
 */
void CatalogTupleUpdate(Relation fc_heapRel, ItemPointer fc_otid, HeapTuple fc_tup)
{
	CatalogIndexState fc_indstate;

	fc_CatalogTupleCheckConstraints(fc_heapRel, fc_tup);

	fc_indstate = CatalogOpenIndexes(fc_heapRel);

	simple_heap_update(fc_heapRel, fc_otid, fc_tup);

	fc_CatalogIndexInsert(fc_indstate, fc_tup);
	CatalogCloseIndexes(fc_indstate);
}

/*
 * CatalogTupleUpdateWithInfo - 与上述相同，但带有调用者提供的索引信息
 *
 * 当跨多个更新时重要的是摊销 CatalogOpenIndexes/CatalogCloseIndexes 的工作时应使用此功能。在某个时刻，我们可能会在某个地方缓存 CatalogIndexState 数据（也许在 relcache 中），以便调用者不必为此烦恼……但我们今天没有这样做。
 */
void CatalogTupleUpdateWithInfo(Relation fc_heapRel, ItemPointer fc_otid, HeapTuple fc_tup,
						   CatalogIndexState fc_indstate)
{
	fc_CatalogTupleCheckConstraints(fc_heapRel, fc_tup);

	simple_heap_update(fc_heapRel, fc_otid, fc_tup);

	fc_CatalogIndexInsert(fc_indstate, fc_tup);
}

/*
 * CatalogTupleDelete - 为删除目录元组执行堆和索引工作
 *
 * 删除指定目录中由“tid”标识的元组。
 *
 * 在 Postgres 堆中，删除时没有索引工作要做；清理将在稍后由 VACUUM 完成。然而，这个函数的调用者不应该知道这一点；我们希望所有目录元组更改都有统一的抽象。因此，提供这个目前很简单的包装。
 *
 * 这个抽象有点泄漏，因为我们没有提供优化的 CatalogTupleDeleteWithInfo 版本，因为目前没有任何东西可以优化。如果我们以后需要这样做，而不是去更改许多调用点，可能更好的是考虑缓存 CatalogIndexState。
 */
void CatalogTupleDelete(Relation fc_heapRel, ItemPointer fc_tid)
{
	simple_heap_delete(fc_heapRel, fc_tid);
}
